﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;

namespace SimpleIR
{
	/// <summary>
	/// This class is used to represent all bytecode operations load a value onto the eval stack in an indirect way (field derefernece, pointer loads, etc.).
	/// </summary>
	public sealed class IRLoadToEvalStackIndirect : IRByteCode
	{
		/// <summary>
		/// An array containing all the acceptable opcodes for this operation class (in order)
		/// </summary>
		private static readonly OpCode.Op[] AcceptableOps = { OpCode.Op.ArrayLoad, OpCode.Op.ArrayLength, OpCode.Op.IndirectRead, OpCode.Op.LoadStructField, OpCode.Op.LoadMemberField };

		/// <summary>
		/// This is the type that we are loading from if this is an array load.
		/// </summary>
		private readonly IRContainerType m_arraytype;

		/// <summary>
		/// This is the type of the pointer we are going to read through.
		/// </summary>
		private readonly IRReferenceType m_reftype;

		/// <summary>
		/// This is the member field that we we are going to read from in a struct field load op.
		/// </summary>
		private readonly IRMemberField m_readfield;

		/// <summary>
		/// Return true if the return value from this call is the get current for a loop traversal.
		/// </summary>
		public bool IsLoopTraverseGet;

		/// <summary>
		/// If the traversal is on a container with then this is the root var 
		/// that we always access the container (or enumerator) from.
		/// </summary>
		public IRVarName ContainerCopyVar;

		/// <summary>
		/// If the traversal is on an container with an indexer then 
		/// this is the optional member field that we always access the container from.
		/// </summary>
		public IRMemberField OptContainerCopyField;

		/// <summary>
		/// The unique operation that the indexer variable can me stored to at (i.e. check this)
		/// </summary>
		public IRStoreFromEvalStackBasic IndexerStoreLoc;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			;
		}

		private IRLoadToEvalStackIndirect(OpCode.Op opc, IRContainerType arraytype, IRReferenceType readthroughtype, IRMemberField readfield, IRSourceLocation sloc)
			: base(opc, sloc)
		{
			Contract.Requires(AcceptableOps.Contains(opc));

			this.m_arraytype = arraytype;
			this.m_reftype = readthroughtype;
			this.m_readfield = readfield;
		}

		public static IRLoadToEvalStackIndirect MakeArrayLengthOperation(IRSourceLocation sloc)
		{
			return new IRLoadToEvalStackIndirect(OpCode.Op.ArrayLength, null, null, null, sloc);
		}

		public static IRLoadToEvalStackIndirect MakeLoadArrayOperation(IRContainerType arraytype, IRSourceLocation sloc)
		{
			Contract.Requires(arraytype != null);

			return new IRLoadToEvalStackIndirect(OpCode.Op.ArrayLoad, arraytype, null, null, sloc);
		}

		public static IRLoadToEvalStackIndirect MakeLoadIndirectOperation(IRReferenceType throughreftype, IRSourceLocation sloc)
		{
			Contract.Requires(throughreftype != null);

			return new IRLoadToEvalStackIndirect(OpCode.Op.IndirectRead, null, throughreftype, null, sloc);
		}

		public static IRLoadToEvalStackIndirect MakeLoadStructFieldOperation(IRMemberField readfield, IRSourceLocation sloc)
		{
			Contract.Requires(readfield != null);

			return new IRLoadToEvalStackIndirect(OpCode.Op.LoadStructField, null, null, readfield, sloc);
		}

		public static IRLoadToEvalStackIndirect MakeLoadClassFieldOperation(IRMemberField readfield, IRSourceLocation sloc)
		{
			Contract.Requires(readfield != null);

			return new IRLoadToEvalStackIndirect(OpCode.Op.LoadMemberField, null, null, readfield, sloc);
		}

		public override String ToString()
		{
			StringBuilder sb = new StringBuilder();
			if(this.OperationCode == OpCode.Op.ArrayLength)
				sb.Append("lenarr:  ");
			else if(this.OperationCode == OpCode.Op.ArrayLoad)
				sb.Append("ldarray: " + this.m_arraytype.ToString());
			else if(this.OperationCode == OpCode.Op.IndirectRead)
				sb.Append("ldind:   " + this.m_reftype.ToString());
			else if(this.OperationCode == OpCode.Op.LoadStructField)
				sb.Append("lsfld:   " + this.m_readfield.ToString());
			else if(this.OperationCode == OpCode.Op.LoadMemberField)
				sb.Append("ldfld:   " + this.m_readfield.ToString());
			else
				sb.Append("--BAD OPCODE--");

			if(this.IsLoopTraverseGet)
			{
				sb.Append(" Get-" + this.ContainerCopyVar.ToString());
				if(this.OptContainerCopyField != null)
					sb.Append("." + this.OptContainerCopyField.ToString());

				if(this.IndexerStoreLoc != null)
					sb.Append("[" + this.IndexerStoreLoc.LocalVar.ToString() + "]");
			}

			return sb.ToString();
		}

		/// <summary>
		/// This is the array type we are going to load from.
		/// </summary>
		public IRContainerType ArrayLoadType
		{
			get
			{
				Contract.Requires(this.OperationCode == OpCode.Op.ArrayLoad);
				Contract.Ensures(Contract.Result<IRContainerType>() != null);

				return this.m_arraytype;
			}
		}

		/// <summary>
		/// This is the ptr type we are going to read through.
		/// </summary>
		public IRReferenceType ThroughRefType
		{
			get
			{
				Contract.Requires(this.OperationCode == OpCode.Op.IndirectRead);
				Contract.Ensures(Contract.Result<IRReferenceType>() != null);

				return this.m_reftype;
			}
		}

		/// <summary>
		/// This is the struct field we are going to load.
		/// </summary>
		public IRMemberField LoadField
		{
			get
			{
				Contract.Requires((this.OperationCode == OpCode.Op.LoadStructField) | (this.OperationCode == OpCode.Op.LoadMemberField));
				Contract.Ensures(Contract.Result<IRMemberField>() != null);

				return this.m_readfield;
			}
		}
	}
}

